本篇將介紹到
我們開始吧!
GoF 說道:
(Decorator 模式)動態地給一個物件一些額外的職責。就增加功能來說,decorator 模式比生成子類別更為靈活。
它的工作原理:
這次我們先來看看 decorator 策略的 UML 圖
本圖隱含了上述的物件鏈:
Component
物件(ConcreteComponent
或 Decorator
)Decorator
物件後面都接著另一個 Decorator
或是 ConcreteComponent
ConcreteComponent
物件Interesting, right?
我相信到這裡可能還是有點模糊,不知該怎麼實踐它。我們接下來就看看案例。
本例是 Day10 提到的電子商務系統,就不在此贅述。
我們當時有說到,當 SalesOrder
計算完商品金額後,它會使用 SalesTicket
幫忙列印票券,如下圖。
今天有了新需求:我們需要為 SalesTicket
物件新增表頭 (header) 與頁腳 (footer)。
簡單的實踐辦法會是:新增 Header
、Footer
物件,讓 SalesTicket
使用該兩個物件, 配上 switch-case 做判斷,來將 header 與 footer 加上去。
但是如果今天的判斷變複雜了:如果今天有多種 headers 與 footers 呢?如果有機會會需要同時有兩種 headers 在同一張票券呢?
多種組合的可能,可能使上述簡單的結構無法承擔。
這時候如果我們引入 decorator 模式,我們就會有下面的圖:
此處的 Client
即是 SalesOrder
。我們接下來來看實踐的程式碼如何。
Client
做的事:
class Client {
public static void main(String[] args) {
Factory myFactory;
myFactory = new Factory();
Component myComponent = myFactory.getComponent();
}
}
Component
抽象類別與其一的衍生類別:SalesTicket
(上述的 ConcreteComponent
):
abstract class Component {
abstract public void printTicket();
}
class SalesTicket extends Component {
public void printTicket() {
// 列印銷售票券的程式碼
}
}
各種 Decorator
:
abstract class TicketDecorator extends Component {
private Component myTrailer;
public TicketDecorator(Component myComponent) {
myTrailer = myComponent;
}
public void callTrailer() {
if (myTrailer != null) myTrailer.printTicker();
}
// 各種具體 Decorator 類別
public class Header1 extends TicketDecorator {
public Header1(Component myComponent) {
super(myComponent);
}
public void printTicket() {
// 列印 Header1 的程式碼
super.callTrailer();
}
}
public class Header2 extends TicketDecorator {
public Header2(Component myComponent) {
super(myComponent);
}
public void printTicket() {
// 列印 Header2 的程式碼
super.callTrailer();
}
}
public class Footer1 extends TicketDecorator {
public Footer1(Component myComponent) {
super(myComponent);
}
public void printTicket() {
super.callTrailer();
// 列印 Footer1 的程式碼
}
}
public class Footer2 extends TicketDecorator {
public Footer2(Component myComponent) {
super(myComponent);
}
public void printTicket() {
super.callTrailer();
// 列印 Footer2 的程式碼
}
}
}
產生物件鏈的工廠物件
class Factory {
public Component getComponent() {
Component myComponent;
myComponent = new SalesTicket();
myComponent = new Footer1(myComponent);
}
}
觀察最後這個 Factory
,我們可以知道 myFacory.getComponent()
會使這張 ticket 印出來的樣式為:
HEADER1
SALES TICKET
FOOTER1
如果今天我們需要的是
HEADER1
HEADER2
SALES TICKET
FOOTER1
那麼 myFactory.getComponent()
所需要的物件鏈就會是
return new Header1(new Header2(new Footer1(new SalesTicket())));
以此類推。
以下是 decorator 模式的關鍵特徵:
項目 | 內容 |
---|---|
意圖 | 動態地給一個物件增加職責 |
問題 | 要使用的物件將執行所需的基礎功能。但可能需要為這個物件增加某些功能,附加功能可能在基礎功能之前或之後。 |
解決方案 | 可以無須建立子類別,而擴展一個物件的功能 |
參與者與協作者 | ConcreteComponent 讓 Decorator 物件為自己增加功能。有時候也可能用 ConcreteComponent 的衍生類別提供核心功能。Component 類別定義了所有上述類別所使用的介面。 |
效果 | 增加的功能放進小物件中。這樣可以動態地在 ConcreteComponent 物件之前或之後新增功能。注意到物件鏈必終於 ConcreteComponet 。 |
實作 | 建立一個抽象類別來表示原類別和要增加到這個類別的新功能。在裝飾類別中,將對新功能的呼叫放在緊隨其後的物件的呼叫之前或之後,以獲得正確的順序。 |
我們要注意到這個模式有幾個約束因素:
Decorator
可遵循也可不遵循所有規則Decorator
物件們,但又不能增加客戶負擔(使客戶增加某些職責)Decorator
物件的職責。試著 follow 上述的約束,可以讓 decorator 模式的意圖與實作分離開來。
接下來,我們將會介紹 Observer 模式。明天見囉!